#ifndef _NETWORK_PACKET_H_
#define _NETWORK_PACKET_H_

#include "config.h"
#include <vector>
#include <string>
#include "Packets.h"

namespace CSNetworkLib
{
	/**
	 * Klasa przeprowadzająca serializacje i dezerializację pakietu.
	 * Struktura pakietu prezentuje się następująco:
	 * Header:
	 *    4 bajty - rozmiar pakietu wliczając to i następne pola
	 *    2 bajty - id pakietu
	 *    2 bajty - CRC16 częsci danych pakietu.
	 *    4 bajty - Numer sekwencji wraz z dodatkowymi informacjami:
     *              najstarsze 4 bity są maską: 0010 - pakiet jest odpowiedźią na inny, 0001 - nadawca czeka na odpowiedź.
	 * Dane (dla każego elementu znajdującego się na liście przy kodowania, aż do końca pakietu przy dekodowaniu):
	 *    1 bajt  - identyfikator typu danych:
	 *              0x01 – char (1 byte)
	 *              0x02 – short (2 bytes)
	 *              0x03 – int (4 bytes)
	 *              0x04 – int64 (8 bytes)
	 *              0x05 – float (4 bytes)
	 *              0x06 – double (8 bytes)
	 *              0x07 – string (x bytes, 0 terminated)
	 *              0x08 – wide string (x bytes, 2x0 terminated)
	 *              0x09 – data (next 2 bytes, size, next bytes data)
	 *              0x0A – data (next 4 bytes, size, next bytes data)
	 *              0x0B – bool – TRUE
	 *              0x0C – bool - FALSE
	 *    kolejne bajty zgodnie z typem danych.
	 */
    class NetworkPacket
    {
        public:
    		/**
    		 * Enum opisujący typ danych.
    		 */
            enum EDataType
            {
                DATA_NONE = 0,          //!< DATA_NONE
                DATA_BOOL,              //!< DATA_BOOL
                DATA_S8,                //!< DATA_S8
                DATA_U8,                //!< DATA_U8
                DATA_S16,               //!< DATA_S16
                DATA_U16,               //!< DATA_U16
                DATA_S32,               //!< DATA_S32
                DATA_U32,               //!< DATA_U32
                DATA_S64,               //!< DATA_S64
                DATA_U64,               //!< DATA_U64
                DATA_FLOAT,             //!< DATA_FLOAT
                DATA_DOUBLE,            //!< DATA_DOUBLE
                DATA_STRING,            //!< DATA_STRING
                DATA_WIDE_STRING,       //!< DATA_WIDE_STRING
                DATA_RAW                //!< DATA_RAW
            };
        public:
            /**
             * Konstruktor domyslny.
             * @param id identyfikator pakietu
             */
            NetworkPacket(enum COMMUNICATION_ID id = PACKET_NONE);
            /**
             * Konstruktor generujący pakiet odpowiedźi. Pakiet odpowiedźi ma SeqId ustawiony na taki sam jak nadawca i odpowiednio ustawiony bit odpowiedźi, identyfikator pakietu jest ignorowany przy przetwarzaniu odpowiedźi.
             * @param request pakiet bazowy
             * @param responseId identyfikator odpowiedźi, dla celów debugowych lub do wysyłania wyjątków.
             */
			NetworkPacket(const NetworkPacket &request, enum COMMUNICATION_ID responseId = PACKET_NONE);
			/**
			 * Destruktor klasy - sprząta pamieć.
			 */
            ~NetworkPacket();
			           
            /**
             * Metoda zastępująca konstruktor kopiujący i operator przypisania, kopiuje pakiet.
             * @param request pakiet do skopiowania.
             */
			void CopyFrom(const NetworkPacket &request);
			
			/**
			 * Metoda zwraca identyfikator pakietu jako uint16.
			 * @return identyfikator pakietu
			 */
            uint16_t GetId() const; 
            /**
             * Metoda ustawia nowy identyfikator pakietu
             * @param id identyfikator pakietu do ustawiania, patrz enum: COMMUNICATION_ID
             */
            void SetId(uint16_t id);
            /**
             * Metoda zwraca SeqId.
             * @return seqId
             */
            uint32_t GetSeq() const; 
            /**
             * Metoda ustawia nowe SeqId, warto zaznaczyć iż pole pozostawione domyslnym zerem zostanie poprawnie utawione przez metodę wysyłająca klasy NetworkProtocol.
             * @param id seqId do ustawienia, Najstarsze 4 bity zostaną obcięte.
             */
            void SetSeq(uint32_t id) const;
            /**
             * Metoda sprawdza czy pakiet jest odpowiedzią na inny.
             * @return true/false
             */
			bool IsResponse() const;
			/**
			 * Metoda ustaiwa flage odpowiedzi w pakiecie.
			 * @param val true/false
			 */
			void SetResponse(bool val) const;
			/**
			 * Metoda sprawdza czy nadawca tego pakietu czeka na odpowiedź.
			 * @return true/false
			 */
			bool IsWaitingForResponse() const;
			/**
			 * Metoda flage "Wait for Response", domyślnie ustawiana przez NetworkProtocol w funkcji SendWait
			 * @param val wartość flagi
			 */
			void SetWaitingForResponse(bool val) const;
			/**
			 * Metoda umiesza dane typu Bool na liście.
			 * @param value true lub false
			 * @return true, jesli dane zostały umieszczone na liście.
			 */
            bool Push_Bool(bool value);
            /**
             * Metoda umiesza dane typu Int8 na liście.
             * @param value liczba zadanego typu
             * @return true, jesli dane zostały umieszczone na liście.
             */
            bool Push_S8(int8_t value);
            /**
             * Metoda umiesza dane typu UInt8 na liście.
             * @param value liczba zadanego typu
             * @return true, jesli dane zostały umieszczone na liście.
             */
            bool Push_U8(uint8_t value);
            /**
             * Metoda umiesza dane typu Int16 na liście.
             * @param value liczba zadanego typu
             * @return true, jesli dane zostały umieszczone na liście.
             */
            bool Push_S16(int16_t value);
            /**
             * Metoda umiesza dane typu UInt16 na liście.
             * @param value liczba zadanego typu
             * @return true, jesli dane zostały umieszczone na liście.
             */
            bool Push_U16(uint16_t value);
            /**
             * Metoda umiesza dane typu Int32 na liście.
             * @param value liczba zadanego typu
             * @return true, jesli dane zostały umieszczone na liście.
             */
            bool Push_S32(int32_t value);
            /**
             * Metoda umiesza dane typu UInt32 na liście.
             * @param value liczba zadanego typu
             * @return true, jesli dane zostały umieszczone na liście.
             */
            bool Push_U32(uint32_t value);
            /**
             * Metoda umiesza dane typu Int64 na liście.
             * @param value liczba zadanego typu
             * @return true, jesli dane zostały umieszczone na liście.
             */
            bool Push_S64(int64_t value);
            /**
             * Metoda umiesza dane typu UInt64 na liście.
             * @param value liczba zadanego typu
             * @return true, jesli dane zostały umieszczone na liście.
             */
            bool Push_U64(uint64_t value);
            /**
             * Metoda umiesza dane typu Float na liście.
             * @param value liczba typu float
             * @return true, jesli dane zostały umieszczone na liście.
             */
            bool Push_Float(float value);
            /**
             * Metoda umiesza dane typu Double na liście.
             * @param value liczba typu double
             * @return true, jesli dane zostały umieszczone na liście.
             */
            bool Push_Double(double value);
            /**
             * Metoda umiesza dane typu String na liście.
             * @param value bufor z danymi do umiesczenia, wymagane zakończenie zerem.
             * @return true, jesli dane zostały umieszczone na liście.
             */
            bool Push_String(const char * value);
            /**
             * Metoda umiesza dane typu WideString na liście.
             * @param value bufor z danymi do umiesczenia, wymagane zakończenie zerem. 
             * @return true, jesli dane zostały umieszczone na liście.
             */
            bool Push_WideString(const uint16_t * value);
            /**
             * Metoda umiesza surowe dane na liście.
             * @param value bufor z danymi
             * @param size rozmiar danych
             * @return true, jesli dane zostały umieszczone na liście.
             */
            bool Push_Data(const uint8_t* value, uint32_t size);

            /**
             * Metoda zwraca ilośc elementów na liście
             * @return uint32
             */
            uint32_t GetCount() const;
            /**
             * Metoda zwraca szacowany rozmiar pakietu po zakodowaniu (rozmiar jest gwarantowany)
             * @return rozmiar pakietu
             */
            uint32_t GetSize() const;
            
            /**
             * Metoda sprawdza typ danych pod odpowiednim elementem listy
             * @param index index elementu (0 - based)
             * @return enum opisujący typ danych lun NONE gdy błedny index
             */
            enum EDataType GetItemType(uint32_t index) const;
            /**
             * Metoda zwraca rozmiar danych pod odpowiednim elementem listy.
             * @param index index elementu (0 - based)
             * @return rozmiar danych lub 0 gdy błedny index
             */
            uint32_t GetItemSize(uint32_t index) const;  
                              
            /**
             * Metoda pobiera dane typu Bool z listy.
             * @param index index elementu (0 - based)
             * @param value referencja na zmienną w której mają zostać zapisane dane.
             * @return true jeśli dane zostały odczytane, false jeśli dane mają inny typ lub index jest błędny.
             */
            bool Get_Bool(uint32_t index, bool &value) const;
            /**
             * Metoda pobiera dane typu Int8 z listy.
             * @param index index elementu (0 - based)
             * @param value referencja na zmienną w której mają zostać zapisane dane.
             * @return true jeśli dane zostały odczytane, false jeśli dane mają inny typ lub index jest błędny.
             */
            bool Get_S8(uint32_t index, int8_t &value) const; 
            /**
             * Metoda pobiera dane typu UInt8 z listy.
             * @param index index elementu (0 - based)
             * @param value referencja na zmienną w której mają zostać zapisane dane.
             * @return true jeśli dane zostały odczytane, false jeśli dane mają inny typ lub index jest błędny.
             */
            bool Get_U8(uint32_t index, uint8_t &value) const; 
            /**
             * Metoda pobiera dane typu Int16 z listy.
             * @param index index elementu (0 - based)
             * @param value referencja na zmienną w której mają zostać zapisane dane.
             * @return true jeśli dane zostały odczytane, false jeśli dane mają inny typ lub index jest błędny.
             */
            bool Get_S16(uint32_t index, int16_t &value) const; 
            /**
             * Metoda pobiera dane typu UInt16 z listy.
             * @param index index elementu (0 - based)
             * @param value referencja na zmienną w której mają zostać zapisane dane.
             * @return true jeśli dane zostały odczytane, false jeśli dane mają inny typ lub index jest błędny.
             */
            bool Get_U16(uint32_t index, uint16_t &value) const; 
            /**
             * Metoda pobiera dane typu Int32 z listy.
             * @param index index elementu (0 - based)
             * @param value referencja na zmienną w której mają zostać zapisane dane.
             * @return true jeśli dane zostały odczytane, false jeśli dane mają inny typ lub index jest błędny.
             */
            bool Get_S32(uint32_t index, int32_t &value) const; 
            /**
             * Metoda pobiera dane typu UInt32 z listy.
             * @param index index elementu (0 - based)
             * @param value referencja na zmienną w której mają zostać zapisane dane.
             * @return true jeśli dane zostały odczytane, false jeśli dane mają inny typ lub index jest błędny.
             */
            bool Get_U32(uint32_t index, uint32_t &value) const; 
            /**
             * Metoda pobiera dane typu Int64 z listy.
             * @param index index elementu (0 - based)
             * @param value referencja na zmienną w której mają zostać zapisane dane.
             * @return true jeśli dane zostały odczytane, false jeśli dane mają inny typ lub index jest błędny.
             */
            bool Get_S64(uint32_t index, int64_t &value) const; 
            /**
             * Metoda pobiera dane typu UInt64 z listy.
             * @param index index elementu (0 - based)
             * @param value referencja na zmienną w której mają zostać zapisane dane.
             * @return true jeśli dane zostały odczytane, false jeśli dane mają inny typ lub index jest błędny.
             */
            bool Get_U64(uint32_t index, uint64_t &value) const; 
            /**
             * Metoda pobiera dane typu Float z listy.
             * @param index index elementu (0 - based)
             * @param value referencja na zmienną w której mają zostać zapisane dane.
             * @return true jeśli dane zostały odczytane, false jeśli dane mają inny typ lub index jest błędny.
             */
            bool Get_Float(uint32_t index, float &value) const; 
            /**
             * Metoda pobiera dane typu Double z listy.
             * @param index index elementu (0 - based)
             * @param value referencja na zmienną w której mają zostać zapisane dane.
             * @return true jeśli dane zostały odczytane, false jeśli dane mają inny typ lub index jest błędny.
             */
            bool Get_Double(uint32_t index, double &value) const; 
            /**
             * Metoda pobiera dane typu String z listy.
             * @param index index elementu (0 - based)
             * @param value bufor do którego ma zostać zapisany string.
             * @param max_size rozmiar bufora
             * @return true jeśli dane zostały odczytane, false jeśli dane mają inny typ, index jest błędny lub dane nie mieszczą się w buforze.
             */
            bool Get_String(uint32_t index, char * value, uint32_t max_size) const; 
            /**
             * Metoda pobiera dane typu WideString z listy.
             * @param index index elementu (0 - based)
             * @param value bufor do którego ma zostać zapisany string.
             * @param max_size rozmiar bufora
             * @return true jeśli dane zostały odczytane, false jeśli dane mają inny typ, index jest błędny lub dane nie mieszczą się w buforze.
             */
            bool Get_WideString(uint32_t index, uint16_t * value, uint32_t max_size) const; 
            /**
             * Metoda pobiera dane typu String z listy.
             * @param index index elementu (0 - based)
             * @param def domyślny string jaki ma zostać zwrócony w przypadku błędu.
             * @return odczytany string lub wartość domyślna jeśli index jest błedny lub dane mają inny typ.
             */
			std::string GetString(uint32_t index, const std::string &def = "") const;
			/**
			 * Metoda pobiera surowe dane z listy.
			 * @param index index elementu (0 - based)
			 * @param value bufor do którego ma zostać zapisany string.
			 * @param max_size rozmiar bufora
			 * @return true jeśli dane zostały odczytane, false jeśli dane mają inny typ, index jest błędny lub dane nie mieszczą się w buforze.
			 */
            bool Get_Data(uint32_t index, uint8_t* value, uint32_t max_size) const; 
            /**
             * Metoda zwraca wskaźnik na surowe dane.
             * @param index index elementu (0 - based)
             * @return
             */
            const uint8_t* Get_DataPtr(uint32_t index) const;
            /**
             * Metoda przeprowadza serializacje pakietu.
             * @param data miejsce na pakiet
             * @param size rozmiar miejsca
             * @return true jeśli pakiet został poprawnie serializowany do bufora.
             */
            bool Serialize(uint8_t *data, uint32_t size) const;
            /**
             * Metoda przeprowadza deserializację pakietu.
             * @param data dane do deserializacji
             * @param size rozmiar danych
             * @return true jeśli pakiet został poprawnie zdeserializowany i CRC16 się zgadza.
             */
            bool UnSerialize(const uint8_t* data, uint32_t size);
            
            /**
             * Metoda czyści pakiet i listę danych.
             */
            void Clear();
			
            /**
             * Metoda oblicza CRC32 pakietu.
             * @return crc32
             */
			uint32_t GetCRC32() const;
            
			/**
			 * Metoda statyczna oblicza CRC16 zadanego bufora danych.
			 * @param data dane
			 * @param size rozmiar danych
			 * @return 0 gdy wystapił błąd, w przeciwnym wypadku CRC16
			 */
            static uint16_t CalculateCRC16(const uint8_t* data, uint32_t size);
        
        private:
            /**
             * Wewnętrzna struktura do przechowywania danych i informacji o nich na liście.
             */
            struct FPacketData
            {
                enum EDataType type;
                uint32_t size;
                union {
                    bool mBool;
                    int8_t mS8;
                    uint8_t mU8;
                    int16_t mS16;
                    uint16_t mU16;
                    int32_t mS32;
                    uint32_t mU32;
                    int64_t mS64;
                    uint64_t mU64;
                    float mFloat;
                    double mDouble;
                    char* mString;
                    uint16_t* mWideString;
                    uint8_t* mData;
                } data;
            };
            uint16_t                  	m_id;
			mutable uint32_t 			m_seq;
			mutable bool 					m_response;
			mutable bool 					m_waiting;
            std::vector<struct FPacketData> m_items;
    };
	
    /**
     *
     * @param x
     * @param network
     * @return
     */
	template<typename T> inline T switchEndian(const T& x, bool network = true)
	{
		static const uint32_t one = 1;
		static const uint8_t  sig = *(char*)&one;
		if (sig == 0) return x; //bigendian

		T ret;
		uint32_t size = sizeof(T);
		uint8_t* src = (uint8_t*)&x;
		uint8_t* dst = (uint8_t*)&ret;

		for(int8_t i=0;i<size;++i)
		{
			dst[i] = src[size-1-i];
		}
		return ret;
	}
}

#endif
